﻿
package extremefx.crypto {
	import flash.utils.ByteArray;	
	import flash.utils.Endian;
	
	/**
	 * @author Marcelo Volmaro
	 */
	 
	public final class XXTea {
		private static const _delta:uint = 0x9E3779B9;
		
		private static function _longArrayToByteArray(data:Vector.<uint>, includeLength:Boolean):ByteArray {
			var len:uint = data.length;
			var n:uint = (len - 1) << 2;
			
			if (includeLength) {
				var m:uint = data[len - 1];
				
				if ((m < n - 3) || (m > n)) {
					return null;
				}
				
				n = m;
			}
			
			var result:ByteArray = new ByteArray();
			result.endian = Endian.LITTLE_ENDIAN;
			
			for (var i:uint = 0; i < len; ++i) {
				result.writeUnsignedInt(data[i]);
			}
			
			if (includeLength) {
				result.length = n;
				return result;
				
			} else {
				return result;
			}
		}
		
		private static function _byteArrayToLongArray(data:ByteArray, includeLength:Boolean):Vector.<uint> {
			var len:uint = data.length;
			var n:uint = len >> 2;
			
			if (len % 4 > 0) {
				++n;
				data.length += (4 - (len % 4));
			}
			
			data.endian = Endian.LITTLE_ENDIAN;
			data.position = 0;
			
			var result:Vector.<uint> = new Vector.<uint>(n);
			for (var i:uint = 0; i < n; ++i) {
				result[i] = data.readUnsignedInt();
			}
			
			if (includeLength) {
				result[n] = len;
			}
			
			data.length = len;
			return result;
		}
		
//		public static function encryptText(pText:String, pKey:String):String {
//			var ba1:ByteArray = new ByteArray();
//			var ba2:ByteArray = new ByteArray();
//			ba1.writeUTFBytes(pText);
//			ba2.writeUTFBytes(pKey);
//			
//			return Hex.fromArray(encrypt(ba1, ba2));
//		}
//		
//		public static function decryptText(pText:String, pKey:String):String {
//			var ba2:ByteArray = new ByteArray();
//			ba2.writeUTFBytes(pKey);
//			
//			var ba:ByteArray = decrypt(Hex.toArray(pText), ba2);
//			return ba.readUTFBytes(ba.length);
//		}
		
		
		/**
		 * Encrypts a stream of bytes using a key
		 * @param data the stream to encrypt
		 * @param key the key used to encrypt the data
		 * @return the encrypted data
		 */
		public static function encrypt(data:ByteArray, key:ByteArray):ByteArray {
			if (data.length == 0) {
				return new ByteArray();
			}
			
			var v:Vector.<uint> = _byteArrayToLongArray(data, true);
			var k:Vector.<uint> = _byteArrayToLongArray(key, false);
			if (k.length < 4) {
				k.length = 4;
			}
			
			var n:uint = v.length - 1;
			var z:uint = v[n];
			var y:uint = v[0];
			var mx:uint;
			var e:uint;
			var p:uint;
			var q:uint = uint(6 + 52 / (n + 1));
			var sum:uint = 0;
			
			while (0 < q--) {
				sum = sum + _delta;
				e = sum >>> 2 & 3;
				
				for (p = 0; p < n; ++p) {
					y = v[p + 1];
					mx = (z >>> 5 ^ y << 2) + (y >>> 3 ^ z << 4) ^ (sum ^ y) + (k[p & 3 ^ e] ^ z);
					z = v[p] = v[p] + mx;
				}
				
				y = v[0];
				mx = (z >>> 5 ^ y << 2) + (y >>> 3 ^ z << 4) ^ (sum ^ y) + (k[p & 3 ^ e] ^ z);
				z = v[n] = v[n] + mx;
			}
			
			return _longArrayToByteArray(v, false);
		}
		
		/**
		 * Decrypts a stream of bytes using an encryption key
		 * @param data the stream to decrypt
		 * @param key the key used to decrypt the stream
		 * @return the decrypted stream
		 */
		
		public static function decrypt(data:ByteArray, key:ByteArray):ByteArray {
			if (data.length == 0) {
				return new ByteArray();
			}
			
			var v:Vector.<uint> = _byteArrayToLongArray(data, false);
			var k:Vector.<uint> = _byteArrayToLongArray(key, false);
			
			if (k.length < 4) {
				k.length = 4;
			}
			
			var n:uint = v.length - 1;
			var z:uint = v[n - 1];
			var y:uint = v[0];
			var mx:uint;
			var e:uint;
			var p:uint;
			var q:uint = uint(6 + 52 / (n + 1));
			var sum:uint = q * _delta;
			
			while (sum != 0) {
				e = sum >>> 2 & 3;
				for (p = n; p > 0; p--) {
					z = v[p - 1];
					mx = (z >>> 5 ^ y << 2) + (y >>> 3 ^ z << 4) ^ (sum ^ y) + (k[p & 3 ^ e] ^ z);
					y = v[p] = v[p] - mx;
				}
				
				z = v[n];
				mx = (z >>> 5 ^ y << 2) + (y >>> 3 ^ z << 4) ^ (sum ^ y) + (k[p & 3 ^ e] ^ z);
				y = v[0] = v[0] - mx;
				sum = sum - _delta;
			}
			
			return _longArrayToByteArray(v, true);
		}
	}
}
